import { Move } from './Move';
import { Board } from './Board';
import { Piece, PieceColor, PieceType } from './Piece';

const { abs, sign } = Math;

export interface RulesConfig {
    //
}

export class Rules {
    constructor(config: RulesConfig) {
        this.config = config;
    }

    public validateMove(board: Board, move: Move, currentPlayer: PieceColor): boolean {
        const source = move.getSourcePiece(board);
        const target = move.getTargetPiece(board);
        // Rule: You cannot move empty or pieces of opponents
        if (source.color !== currentPlayer || source.color === PieceColor.BLANK) {
            return false;
        }
        // Is target empty?
        if (target.color === PieceColor.BLANK) {
            return Rules.isPathClear(board, source, target);
        } else {
            // Rule: You cannot take pieces of your own
            if (target.color === currentPlayer) {
                return false;
            } else {
                if (source.type === PieceType.CANNON) {
                    return Rules.isCannonShootable(board, source, target);
                } else {
                    return Rules.isPathClear(board, source, target);
                }
            }
        }
    }

    private static isPathClear(board: Board, source: Piece, target: Piece): boolean {
        switch (source.type) {
            case PieceType.PAWN:
                return Rules.isPawnMovable(board, source, target);
            case PieceType.CANNON:
                return Rules.isCannonMovable(board, source, target);
            case PieceType.ROOK:
                return Rules.isRookMovable(board, source, target);
            case PieceType.KNIGHT:
                return Rules.isKnightMovable(board, source, target);
            case PieceType.BISHOP:
                return Rules.isBishopMovable(board, source, target);
            case PieceType.ADVISOR:
                return Rules.isAdvisorMovable(source, target);
            case PieceType.GENERAL:
                return Rules.isGeneralMovable(board, source, target);
            default:
                return false;
        }
    }

    private static isPawnMovable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.PAWN) {
            return false;
        }
        const diffRow = target.row - source.row;
        const diffCol = target.col - source.col;
        if (abs(diffRow) === 1 && abs(diffCol) === 0) {
            if (source.color === PieceColor.DARK) {
                return diffRow === 1;
            } else if (source.color === PieceColor.RED) {
                return diffRow === -1;
            } else {
                return false;
            }
        } else if (abs(diffRow) === 0 && abs(diffCol) === 1) {
            return this.isPawnCrossedRiver(source);
        } else {
            return false;
        }
    }

    private static isRookMovable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.ROOK) {
            return false;
        }
        if (this.isPathHorizontal(source, target) || this.isPathVertical(source, target)) {
            return this.countObstructionsAlongPath(board, source, target) === 0;
        } else {
            return false;
        }
    }

    private static isKnightMovable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.KNIGHT) {
            return false;
        }
        const diffRow = target.row - source.row;
        const diffCol = target.col - source.col;
        if (abs(diffCol) === 2 && abs(diffRow) === 1) {
            return board.getPieceAt(source.row, source.col + sign(diffCol)).type === PieceType.EMPTY;
        } else if (abs(diffCol) === 1 && abs(diffRow) === 2) {
            return board.getPieceAt(source.row + sign(diffRow), source.col).type === PieceType.EMPTY;
        } else {
            return false;
        }
    }

    private static isBishopMovable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.BISHOP) {
            return false;
        }
        if (this.isPositionAtOtherSide(source.color, target.row)) {
            return false;
        } else {
            return board.getPieceAt(source.row + sign(target.row - source.row), source.col + sign(target.col - source.col)).type === PieceType.EMPTY
                && (abs(target.row - source.row) === 2 && abs(target.col - source.col) === 2);
        }
    }

    private static isAdvisorMovable(source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.ADVISOR) {
            return false;
        }
        const diffRow = target.row - source.row;
        const diffCol = target.col - source.col;
        const { row, col } = target;
        return this.isPositionInPalace(source.color, row, col)
            && (abs(diffRow) ===  1 && abs(diffCol) === 1);
    }

    private static isGeneralMovable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.GENERAL) {
            return false;
        }
        const diffRow = target.row - source.row;
        const diffCol = target.col - source.col;
        const { row, col } = target;
        if (target.type === PieceType.GENERAL) {
            return this.countObstructionsAlongPath(board, source, target) === 0;
        } else {
            return this.isPositionInPalace(source.color, row, col)
                && ((abs(diffRow) === 1 && abs(diffCol) === 0) || (abs(diffRow) === 0 && abs(diffCol) === 1));
        }
    }

    private static isCannonMovable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.CANNON) {
            return false;
        }
        if (Rules.isPathVertical(source, target) || Rules.isPathHorizontal(source, target)) {
            return this.countObstructionsAlongPath(board, source, target) === 0;
        } else {
            return false;
        }
    }

    private static isCannonShootable(board: Board, source: Piece, target: Piece): boolean {
        if (source.type !== PieceType.CANNON) {
            return false;
        }
        if (Rules.isPathVertical(source, target) || Rules.isPathHorizontal(source, target)) {
            return this.countObstructionsAlongPath(board, source, target) === 1;
        } else {
            return false;
        }
    }

    private static countObstructionsAlongPath(board: Board, source: Piece, target: Piece): number {
        let count = 0;
        if (Rules.isPathHorizontal(source, target)) {
            const step = sign(target.col - source.col);
            const row = source.row;
            for (let i = source.col + step; i !== target.col; i += step) {
                if (board.getPieceAt(row, i).type !== PieceType.EMPTY) {
                    ++count;
                }
            }
        } else if (Rules.isPathVertical(source, target)) {
            const step = sign(target.row - source.row);
            const col = source.col;
            for (let i = source.row + step; i !== target.row; i += step) {
                if (board.getPieceAt(i, col).type !== PieceType.EMPTY) {
                    ++count;
                }
            }
        } else if (Rules.isPathDiagonal(source, target)) {
            const step = sign(target.row - source.row);
            for (let i = source.row + step; i !== target.row; i += step) {
                if (board.getPieceAt(i, i).type !== PieceType.EMPTY) {
                    ++count;
                }
            }
        } else {
            throw new Error('Do not call this method on non-directional move');
        }
        return count;
    }

    private static isPathHorizontal(source: Piece, target: Piece): boolean {
        return source.row - target.row === 0;
    }

    private static isPathVertical(source: Piece, target: Piece): boolean {
        return source.col - target.col === 0;
    }

    private static isPathDiagonal(source: Piece, target: Piece): boolean {
        return Math.abs(source.col - target.col) === Math.abs(source.row - target.row);
    }

    private static isPawnCrossedRiver(source: Piece): boolean {
        if (source.type !== PieceType.PAWN) {
            return false;
        }
        if (source.color === PieceColor.RED) {
            return source.row < 5;
        } else if (source.color === PieceColor.DARK) {
            return source.row > 4;
        } else {
            return false;
        }
    }

    private static isPositionAtOtherSide(color: PieceColor, row: number): boolean {
        if (color === PieceColor.RED) {
            return row < 5;
        } else if (color === PieceColor.DARK) {
            return row > 4;
        } else {
            return false;
        }
    }

    private static isPositionInPalace(color: PieceColor, row: number, col: number) {
        if (color === PieceColor.RED) {
            return Rules.inRange(row, 7, 9) && Rules.inRange(col, 3, 5);
        } else if (color === PieceColor.DARK) {
            return Rules.inRange(row, 0, 2) && Rules.inRange(col, 3, 5);
        } else {
            return false;
        }
    }

    private static inRange(v: number, l: number, h: number) {
        return v >= l && v <= h;
    }

    private readonly config: RulesConfig;
}

export const defaultRules = {};
